/*
 * Scaffold Application Framework
 *
 * Copyright (c) 2010 Sii Sp. z o.o. All rights reserved.
 */
package com.gdziarmaga.webshop.web.utils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.support.OpenSessionInViewFilter;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.context.support.WebApplicationContextUtils;


/**
 * Has helper methods for JSF.
 * 
 * @author Wojciech Kochman
 * @author Pawel Burno
 * @version 1.5
 * 
 */
public final class FacesUtils {

    /** Private Constructor for utility class. */
    private FacesUtils() {
    }

    /**
     * Gets the request.
     * 
     * @return the request
     */
    public static HttpServletRequest getRequest() {
        return ((HttpServletRequest) getExternalContext().getRequest());
    }

    /**
     * Gets the response.
     * 
     * @return the response
     */
    public static HttpServletResponse getResponse() {
        return ((HttpServletResponse) getExternalContext().getResponse());
    }

    /**
     * Gets the {@link Locale} form request.
     * 
     * @return the Locale
     */
    public static Locale getLocale() {
        return getRequest().getLocale();
    }


    /**
     * Gets external context.
     * 
     * @return the external context
     */
    public static ExternalContext getExternalContext() {
        return FacesContext.getCurrentInstance().getExternalContext();
    }

    /**
     * Gets the {@link UIComponent} based on given id.
     * 
     * @param id the id of component
     * @return the component
     */
    public static UIComponent getComponent(String id) {
        return FacesContext.getCurrentInstance().getViewRoot().findComponent(id);
    }

    /**
     * Gets the current application.
     * 
     * @return the application
     */
    public static Application getApplication() {
        return FacesContext.getCurrentInstance().getApplication();
    }

    /**
     * Adds new {@link FacesMessage} to the context.
     * 
     * @param severity the severity
     * @param summary the message text
     * @param details detailed message text
     */
    private static void addFacesMessage(Severity severity, String summary, String details) {
        addFacesMessage(null, severity, summary, details);
    }

    /**
     * Adds new {@link FacesMessage} to the context.
     * 
     * @param severity the severity
     * @param summary the message text
     * @param details detailed message text
     * @param clientId the id of the component to add message
     */
    private static void addFacesMessage(String clientId, Severity severity, String summary, String details) {
        FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(severity, summary, details));
    }

    /**
     * Adds an info {@link FacesMessage} to the context.
     * 
     * @param summary the message text
     */
    public static void addFacesInfoMessage(String summary) {
        addFacesMessage(FacesMessage.SEVERITY_INFO, summary, null);
    }

    /**
     * Adds a warning {@link FacesMessage} to the context.
     * 
     * @param summary the message text
     */
    public static void addFacesWarnMessage(String summary) {
        addFacesMessage(FacesMessage.SEVERITY_WARN, summary, null);
    }

    /**
     * Adds an error {@link FacesMessage} to the context.
     * 
     * @param summary the message text
     * @param e the Exception to create detailed message
     */
    public static void addFacesErrorMessage(String summary, Exception e) {
        StringBuilder sb = new StringBuilder();
        createStackTrace(sb, e, new StackTraceElement[]{});
        addFacesErrorMessage(summary, sb.toString());
    }

    /**
     * Creates a stack trace from given Exception.
     * 
     * @param sb the {@link StringBuilder} to fill in with stack trace
     * @param e the Exception
     * @param causedTrace the stack trace of cause
     */
    private static void createStackTrace(StringBuilder sb, Throwable e, StackTraceElement[] causedTrace) {
        sb.append("Caused by: " + e);
        StackTraceElement[] trace = e.getStackTrace();
        int m = trace.length - 1, n = causedTrace.length - 1;
        while (m >= 0 && n >= 0 && trace[m].equals(causedTrace[n])) {
            m--;
            n--;
        }
        for (StackTraceElement s : trace) {
            sb.append("\tat " + s);
        }
        int framesInCommon = trace.length - 1 - m;
        if (framesInCommon != 0) {
            sb.append("\t... " + framesInCommon + " more");
        }
        if (e.getCause() != null) {
            createStackTrace(sb, e.getCause(), trace);
        }
    }

    /**
     * Adds an error {@link FacesMessage} to the context.
     * 
     * @param clientId the id of the component to add message
     * @param summary the message text
     * @param details detailed message text
     */
    public static void addFacesErrorMessage(String clientId, String summary, String details) {
        addFacesMessage(clientId, FacesMessage.SEVERITY_ERROR, summary, details);
    }

    /**
     * Adds an error {@link FacesMessage} to the context.
     * 
     * @param summary the message text
     * @param details detailed message text
     */
    public static void addFacesErrorMessage(String summary, String details) {
        addFacesMessage(FacesMessage.SEVERITY_ERROR, summary, details);
    }

    /**
     * Adds an error {@link FacesMessage} to the context.
     * 
     * @param summary the message text
     */
    public static void addFacesErrorMessage(String summary) {
        addFacesMessage(FacesMessage.SEVERITY_ERROR, summary, null);
    }

    /**
     * Adds a fatal {@link FacesMessage} to the context.
     * 
     * @param summary the message text
     */
    public static void addFacesFatalMessage(String summary) {
        addFacesMessage(FacesMessage.SEVERITY_FATAL, summary, null);
    }

    /**
     * Gets {@link Locale} supported by the application.
     * 
     * @return the Iterator of supported locales
     */
    public static Iterator<Locale> getSupportedLocales() {
        return FacesContext.getCurrentInstance().getApplication().getSupportedLocales();
    }

    /**
     * Gets the default {@link Locale} of the application.
     * 
     * @return the default Locale.
     */
    public static Locale getDefaultLocale() {
        return FacesContext.getCurrentInstance().getApplication().getDefaultLocale();
    }

    /**
     * Gets a context path.
     * 
     * @return the context path.
     */
    public static String getContextPath() {
        return getRequest().getContextPath();
    }

    /**
     * Gets a server name.
     * 
     * @return the server name
     */
    public static String getServerName() {
        return getRequest().getServerName();
    }

    /**
     * Gets a server port.
     * 
     * @return the server port
     */
    public static String getServerPort() {
        return String.valueOf(getRequest().getServerPort());
    }

    /**
     * Gets a servlet context.
     * 
     * @return the servlet context
     */
    public static ServletContext getServletContext() {
        return getRequest().getSession().getServletContext();
    }

    /**
     * Gets hibernate {@link Session}.
     * 
     * @return the session.
     */
    public static Session getHibernateSession() {
        return SessionFactoryUtils.getSession(WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()).getBean(
                OpenSessionInViewFilter.DEFAULT_SESSION_FACTORY_BEAN_NAME, SessionFactory.class), false);
    }

    /**
     * Gets an application context.
     * 
     * @return the application context
     */
    public static ApplicationContext getApplicationContext() {
        return WebApplicationContextUtils.getWebApplicationContext((ServletContext) getExternalContext().getContext());
    }

    /**
     * Gets a server path.
     * 
     * @return the server path
     */
    public static String getServerPath() {
        String prefix = getServerPort().equals("443") ? "https://" : "http://";
        return prefix + getServerName() + ":" + getServerPort() + getContextPath() + "/";
    }

    /**
     * Gets attribute from request.
     * 
     * @param key - attribute name
     * @return the attribute value
     */
    public static Object getRequestAttribute(String key) {
        return getRequest().getAttribute(key);
    }

    /**
     * Gets parameter from request.
     * 
     * @param key - parameter name
     * @return the parameter value
     */
    public static String getRequestParameter(String key) {
        return getExternalContext().getRequestParameterMap().get(key);
    }

    /**
     * Sets parameter in request map.
     * 
     * @param key parameters name
     * @param value parameters value
     */
    public static void setRequestMapParameter(String key, Object value) {
        getExternalContext().getRequestMap().put(key, value);
    }

    /**
     * Gets parameter from request map.
     * 
     * @param key parameter's name
     * @return the parameter's value
     */
    public static Object getRequestMapParameter(String key) {
        return getExternalContext().getRequestMap().get(key);
    }

    /**
     * Removes parameter from request map.
     * 
     * @param key parameter's name
     * @return the parameter's value
     */
    public static Object removeRequestMapParameter(String key) {
        return getExternalContext().getRequestMap().remove(key);
    }

    /**
     * Sets parameter in session.
     * 
     * @param key parameter's name
     * @param value the parameter's value
     */
    public static void setSessionParameter(String key, Object value) {
        getExternalContext().getSessionMap().put(key, value);
    }

    /**
     * Gets parameter from session.
     * 
     * @param key - parameter name
     * @return the parameter value
     */
    public static Object getSessionParameter(String key) {
        return getExternalContext().getSessionMap().get(key);
    }

    /**
     * Removes parameter from session.
     * 
     * @param key parameter's name
     */
    public static void removeSessionParameter(String key) {
        getExternalContext().getSessionMap().remove(key);
    }

    /**
     * Redirects to specified url.
     * 
     * @param url the url to redirect.
     * @throws IOException when occurs
     * @throws ServletException when occurs
     */
    public static void redirect(String url) throws ServletException, IOException {
        getExternalContext().redirect(new StringBuilder().append(FacesUtils.getContextPath()).append(url).toString());
    }
    
    public static Flash flashScope() {
        return (FacesContext.getCurrentInstance().getExternalContext().getFlash());
  }


//    /**
//     * Removes session beans according to names defined in
//     * {@link SessionBeans#SESSION_BEANS}.
//     */
//    public static void removeSessionBeans() {
//        for (String beanName : SessionBeans.SESSION_BEANS_TO_REMOVE) {
//            removeSessionParameter(beanName);
//        }
//    }

    /**
     * Finds a component base on client id.
     * 
     * @param context the {@link FacesContext}
     * @param component the component to start the search
     * @param id the id of searched component
     * @return the found component
     */
    public static UIComponent findComponent(FacesContext context, UIComponent component, String id) {
        if (id.equals(component.getClientId(context))) {
            return component;
        }
        Iterator<UIComponent> kids = component.getFacetsAndChildren();
        while (kids.hasNext()) {
            UIComponent found = findComponent(context, kids.next(), id);
            if (found != null) {
                return found;
            }
        }
        return null;
    }

    /**
     * Finds a component base on id.
     * 
     * @param component the component to start the search
     * @param id the id of searched component
     * @return the found component
     */
    public static UIComponent findComponent(UIComponent component, String id) {
        if (id.equals(component.getId())) {
            return component;
        }
        Iterator<UIComponent> kids = component.getFacetsAndChildren();
        while (kids.hasNext()) {
            UIComponent found = findComponent(kids.next(), id);
            if (found != null) {
                return found;
            }
        }
        return null;
    }

    /**
     * Puts request related attribute into map
     * <code>sessionUriAssociatedParameterMap</code> stored in session.<br />
     * The attributes are managed by {@link ClearSessionParametrsPhasesListener}
     * .
     * 
     * @param url the Url
     * @param key the parameter's key
     * @param value the parameter's value
     */
    @SuppressWarnings("unchecked")
    public static void putRequestSessionAttribute(String url, String key, Object value) {
        Map<String, Object> sessionMap = (Map<String, Object>) getSessionParameter("sessionUriAssociatedParameterMap");
        if (sessionMap == null) {
            sessionMap = new HashMap<String, Object>();
            setSessionParameter("sessionUriAssociatedParameterMap", sessionMap);
        }
        Map<String, Object> map = (Map<String, Object>) sessionMap.get(getContextPath() + url);
        if (map == null) {
            map = new HashMap<String, Object>();
            map.put("url", FacesUtils.getRequest().getRequestURI());
            sessionMap.put(getContextPath() + url, map);
        }
        map.put(key, value);
        map.put("validOneRequest", false);
    }

    /**
     * Removes parameter from map <code>sessionUriAssociatedParameterMap</code>
     * stored in session.<br />
     * The attributes are managed by {@link ClearSessionParametrsPhasesListener}
     * .
     * 
     * @param key the parametr's key
     */
    @SuppressWarnings("unchecked")
    public static void removeRequestSessionParameterMap(String key) {
        Map<String, Object> sessionMap = (Map<String, Object>) getSessionParameter("sessionUriAssociatedParameterMap");
        if (sessionMap != null && !sessionMap.isEmpty()) {
            sessionMap.remove(key);
        }
    }

    /**
     * Puts request related attribute into map
     * <code>sessionUriAssociatedParameterMap</code> stored in session.<br />
     * The attributes are managed by {@link ClearSessionParametrsPhasesListener}
     * .<br />
     * The attribute is valid only for one request.
     * 
     * @param url the Url
     * @param key the parameter's key
     * @param value the parameter's value
     */
    @SuppressWarnings("unchecked")
    public static void putRequestSessionAttributeValidForOneRequest(String url, String key, Object value) {
        Map<String, Object> sessionMap = (Map<String, Object>) getSessionParameter("sessionUriAssociatedParameterMap");
        if (sessionMap == null) {
            sessionMap = new HashMap<String, Object>();
            setSessionParameter("sessionUriAssociatedParameterMap", sessionMap);
        }
        Map<String, Object> map = (Map<String, Object>) sessionMap.get(getContextPath() + url);
        if (map == null) {
            map = new HashMap<String, Object>();
            map.put("url", FacesUtils.getRequest().getRequestURI());
            sessionMap.put(getContextPath() + url, map);
        }
        map.put(key, value);
        map.put("validOneRequest", true);
    }

    /**
     * Removes parameter from map <code>sessionUriAssociatedParameterMap</code>
     * stored in session.<br />
     * The attributes are managed by {@link ClearSessionParametrsPhasesListener}
     * .
     * 
     * @param key the parametr's key
     */
    @SuppressWarnings("unchecked")
    public static void removeRequestSessionParameterMapValidOneRequest(String key) {
        Map<String, Object> sessionMap = (Map<String, Object>) getSessionParameter("sessionUriAssociatedParameterMap");
        if (sessionMap != null && !sessionMap.isEmpty()) {
            Map<String, Object> paramMap = (Map<String, Object>) sessionMap.get(key);
            if (paramMap != null && !paramMap.isEmpty() && (Boolean) paramMap.get("validOneRequest")) {
                sessionMap.remove(key);
            }
        }
    }

    /**
     * Gets the parameter from map <code>sessionUriAssociatedParameterMap</code>
     * stored in session.
     * 
     * @param key the parameter's name
     * @return the parameter
     */
    @SuppressWarnings("unchecked")
    public static Object getRequestSessionAttribute(String key) {
        Object result = null;
        Map<String, Object> sessionMap = (Map<String, Object>) getSessionParameter("sessionUriAssociatedParameterMap");
        Map<String, Object> map = null;
        if (sessionMap != null && !sessionMap.isEmpty()) {
            map = (Map<String, Object>) sessionMap.get(FacesUtils.getRequest().getRequestURI());
            if (map != null && !map.isEmpty()) {
                result = map.get(key);
            }
        }
        return result;
    }

    /**
     * Gets referer uri from session. <br/>
     * The uri is put to session by {@link ClearSessionParametrsPhasesListener}
     * 
     * @return the {@link String} uri
     */
    public static String getRefererUri() {
        String referer = (String) getSessionParameter("referer");
        if (referer != null) {
            referer = referer.substring(referer.indexOf(getContextPath()));
        }
        return referer;
    }

    /**
     * Gets the referer related parameter map form session map
     * <code>sessionUriAssociatedParameterMap</code>.
     * 
     * @return the map of parameters
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> getRefererRequestSessionAttributeMap() {
        Map<String, Object> sessionMap = (Map<String, Object>) getSessionParameter("sessionUriAssociatedParameterMap");
        Map<String, Object> map = null;
        if (sessionMap != null && !sessionMap.isEmpty()) {
            map = (Map<String, Object>) sessionMap.get(getRefererUri());
        }
        return map;
    }

    /**
     * Clears session map <code>sessionUriAssociatedParameterMap</code>.
     */
    @SuppressWarnings("unchecked")
    public static void clearRequestSessionParameterMap() {
        Map<String, Object> sessionMap = (Map<String, Object>) getSessionParameter("sessionUriAssociatedParameterMap");
        if (sessionMap != null && !sessionMap.isEmpty()) {
            sessionMap.clear();
        }
    }

    /**
     * Removes from session map <code>sessionUriAssociatedParameterMap</code>
     * parameters for url other then specified.
     * 
     * @param uri the request url for which parameters should not be removed
     */
    @SuppressWarnings("unchecked")
    public static void clearRequestSessionParameterMap(String uri) {
        Map<String, Object> sessionMap = (Map<String, Object>) getSessionParameter("sessionUriAssociatedParameterMap");
        if (sessionMap != null && !sessionMap.isEmpty()) {
            List<String> temp = new ArrayList<String>();
            for (String url : sessionMap.keySet()) {
                if (!uri.equals(url)) {
                    temp.add(url);
                }
            }
            for(String url : temp){
                sessionMap.remove(url);
            }
        }
    }

    /**
     * Gets logged {@link User} from spring security context.
     * 
     * @return the logged {@link User}
     */
    public static User getLoggedUser() {
        User user = (User) (SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        return user;
    }

//    public static List<String> getValidationSet() {
//        ExternalContext externalContext = getExternalContext();
//        Map<String, Object> sessionMap = externalContext.getSessionMap();
//        List<String> validationSet = (List<String>) sessionMap.get(SetValidationStylePhaseListener.SESSION_VALIDATION_SET);
//        return validationSet;
//    }
//    public static List<String> getRemoveValidationSet() {
//        ExternalContext externalContext = getExternalContext();
//        Map<String, Object> sessionMap = externalContext.getSessionMap();
//        List<String> validationSet = (List<String>) sessionMap.get(SetValidationStylePhaseListener.SESSION_REMOVE_VALIDATION_SET);
//        return validationSet;
//    }
}
